use serde::{Deserialize, Serialize};
use ts_rs::TS;

use crate::model::{kline::Kline, up2int};
use anyhow::Result;
///
/// Struct to hold the results of Struct [TrendLine]
///
///
#[derive(Serialize, Deserialize, Default, Debug, TS, Clone)]
#[ts(export)]
pub struct TRENDLINE {
    /// TrendLine id , refer to [KlineTrendLine](crate::model::kline::KlineTrendLine)
    pub trend_line_id: String,

    /// TrendLine Signal
    pub signal0: TrendLineSignal,
}

// Enum to select the source value for the indicator calculation.
#[derive(Serialize, Deserialize, Debug, TS, Clone)]
pub enum Source {
    Close,
    Open,
    High,
    Low,
}

#[derive(Serialize, Deserialize, Debug, TS, Clone)]
pub enum TrendLineSignal {
    CrossAbove(u8),
    CrossUnder(u8),
    None,
}

impl Default for TrendLineSignal {
    fn default() -> Self {
        TrendLineSignal::None
    }
}

pub struct CrossTrendLineIndicator {
    init_kline1: Kline,
    init_kline2: Kline,
    span: f64,
    source: Source,
}

impl CrossTrendLineIndicator {
    ///
    /// kline1: 第一个点
    ///
    /// kline2: 第二个点
    ///
    /// span: kline2 线与 kline1 线之间的距离 也就是他们在Klines的索引值之差
    ///
    pub fn new(kline1: &Kline, kline2: &Kline, span: f64, source: Source) -> Self {
        CrossTrendLineIndicator {
            init_kline1: kline1.clone(),
            init_kline2: kline2.clone(),
            span,
            source,
        }
    }

    pub fn init(&self, init_kline: &Kline) -> Result<CrossTrendLineIndicatorInstance> {
        let (slope, intercept) = self.calculate_slope_intercept()?;
        let initial_value = self.calculate_initial_value(init_kline)?;

        Ok(CrossTrendLineIndicatorInstance {
            slope,
            intercept,
            source: self.source.clone(),
            previous_source_value: Some(initial_value),
            current_source_value: Some(initial_value), // Assuming we want to track the current value too.
            current_index: 0,
        })
    }

    fn calculate_slope_intercept(&self) -> Result<(f64, f64)> {
        let duration = self
            .init_kline2
            .klts
            .signed_duration_since(self.init_kline1.klts);
        let delta_time = duration.num_seconds() as f64;

        // Ensure the calculation is valid by checking the duration is positive
        if delta_time <= 0.0 {
            return Err(anyhow::anyhow!(
                "The timestamp of the second Kline must be greater than the first Kline for a valid duration."
            ));
        }

        let value_diff = match self.source {
            Source::Close => (self.init_kline2.close - self.init_kline1.close) as f64,
            Source::Open => (self.init_kline2.open - self.init_kline1.open) as f64,
            Source::High => (self.init_kline2.high - self.init_kline1.high) as f64,
            Source::Low => (self.init_kline2.low - self.init_kline1.low) as f64,
        };

        let slope = value_diff / self.span as f64;
        // b = y - mx
        let intercept = self.init_kline2.close as f64 - slope * self.span as f64;

        Ok((slope, intercept))
    }

    fn calculate_initial_value(&self, kline: &Kline) -> Result<f64> {
        Ok(match self.source {
            Source::Close => kline.close as f64,
            Source::Open => kline.open as f64,
            Source::High => kline.high as f64,
            Source::Low => kline.low as f64,
        })
    }
}

#[derive(Debug)]
pub struct CrossTrendLineIndicatorInstance {
    slope: f64,
    intercept: f64,
    source: Source,
    previous_source_value: Option<f64>,
    current_source_value: Option<f64>,
    current_index: u32,
}

impl CrossTrendLineIndicatorInstance {
    pub fn next(&mut self, next_kline: &Kline) -> TrendLineSignal {
        let current_value = match self.source {
            Source::Close => next_kline.close as f64,
            Source::Open => next_kline.open as f64,
            Source::High => next_kline.high as f64,
            Source::Low => next_kline.low as f64,
        };

        self.current_index += 1;
        self.previous_source_value = self.current_source_value;
        self.current_source_value = Some(current_value);

        let line_y_at_x = self.slope * self.current_index as f64 + self.intercept;
        let was_previous_above = self.previous_source_value.unwrap_or_default() > line_y_at_x;
        let is_current_above = current_value > line_y_at_x;

        // Calculate scaled percentage deviation if there's a cross-over or cross-under
        let signal = if is_current_above != was_previous_above {
            let deviation = ((line_y_at_x - current_value) / line_y_at_x * 100.0)
                .abs()
                .round() as u8;

            if is_current_above {
                TrendLineSignal::CrossAbove(deviation)
            } else {
                TrendLineSignal::CrossUnder(deviation)
            }
        } else {
            TrendLineSignal::None
        };

        signal
    }

    pub fn get_straight_trend_line(&self, klines: &Vec<Kline>) -> Result<Vec<Kline>> {
        // Using `max_by` to find the Kline with the maximum close value:
        // Using `max_by_key` to find the Kline with the maximum close value:
        let y_max_kline = klines.iter().max_by_key(|kline| up2int(kline.close));
        let y_min_kline = klines.iter().min_by_key(|kline| up2int(kline.close));
        let len = klines.len();
        let mut lines = vec![];

        if let (Some(y_max), Some(y_min)) = (y_max_kline, y_min_kline) {
            for (idx, kline) in klines.iter().enumerate() {
                // y = mx +b
                let y = self.slope * (50.0 - idx as f64) + self.intercept;
                // if y_min.close <= y && y <= y_max.close {
                let kl = Kline {
                    id: kline.id.clone(),
                    klt: kline.klt,
                    klts: kline.klts,
                    close: y,
                    ..Default::default()
                };
                lines.push(kl);
                // }
            }
        }

        Ok(lines)
    }
}

#[cfg(test)]
mod test {

    use chrono::{Duration, Utc};
    use surrealdb::sql::Thing;

    use crate::model::{kline::Kline, FudaRecord};

    use super::{CrossTrendLineIndicator, Source};

    // Assuming you have a Vec<Kline> named `my_klines`, you would call:
    // analyze_klines(&my_klines);
    #[test]
    fn test_cross_trend_line_indicator() {
        // Create dummy Kline data
        let klines = vec![
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.30".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(30),
                open: 100.0,
                high: 102.0,
                low: 99.5,
                close: 100.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.25".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(25),
                open: 101.0,
                high: 103.0,
                low: 100.5,
                close: 100.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.15".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(15),
                open: 103.0,
                high: 105.0,
                low: 102.5,
                close: 90.0,
                ..Default::default() // This close value crosses above the trend line established by the first two Klines
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.10".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(15),
                open: 103.0,
                high: 105.0,
                low: 102.5,
                close: 110.0,
                ..Default::default() // This close value crosses above the trend line established by the first two Klines
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.10".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(15),
                open: 103.0,
                high: 105.0,
                low: 102.5,
                close: 112.0,
                ..Default::default() // This close value crosses above the trend line established by the first two Klines
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.10".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(15),
                open: 103.0,
                high: 105.0,
                low: 102.5,
                close: 99.0,
                ..Default::default() // This close value crosses above the trend line established by the first two Klines
            },
        ];

        let ctli = CrossTrendLineIndicator::new(&klines[0], &klines[1], 1.0, Source::Close);

        let mut ctli_inst = ctli.init(&klines[2]).unwrap();

        for (idx, kline) in klines[3..].iter().enumerate() {
            let rs = ctli_inst.next(&kline);
            println!(
                "[{}]{:?} kline.close={:?} ctli_inst={:?}",
                idx + 3,
                rs,
                kline.close,
                ctli_inst
            )
        }
    }

    // Assuming you have a Vec<Kline> named `my_klines`, you would call:
    // analyze_klines(&my_klines);
    #[test]
    fn test_cross_trend_line_indicator_straight_line() {
        // Create dummy Kline data
        let klines = vec![
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.30".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(30),
                open: 100.0,
                high: 102.0,
                low: 99.5,
                close: 5.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.25".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(25),
                open: 101.0,
                high: 103.0,
                low: 100.5,
                close: 20.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
            Kline {
                id: Some(Thing {
                    id: surrealdb::sql::Id::String("SH.STOCK.600519.20".to_string()),
                    tb: "Kline".to_string(),
                }),
                klts: Utc::now() - Duration::minutes(20),
                open: 102.0,
                high: 104.0,
                low: 101.5,
                close: 80.0,
                ..Default::default()
            },
        ];

        let ctli = CrossTrendLineIndicator::new(&klines[0], &klines[1], 1.0, Source::Close);
        let ctli_inst = ctli.init(&klines[2]).unwrap();

        println!("ctli_inst: {:?} klines.len={}", ctli_inst, klines.len());
        let rs = ctli_inst.get_straight_trend_line(&klines).unwrap();

        for (idx, kline) in rs.iter().enumerate() {
            println!("idx={},kline.close={:?}", idx, kline.close);
        }
    }
}
